package org.hackystat.devcathlon.engine.event.impl;

import java.util.HashMap;
import java.util.Map;
import javax.xml.datatype.XMLGregorianCalendar;
import org.hackystat.dailyprojectdata.client.DailyProjectDataClient;
import org.hackystat.dailyprojectdata.resource.devtime.jaxb.DevTimeDailyProjectData;
import org.hackystat.dailyprojectdata.resource.devtime.jaxb.MemberData;
import org.hackystat.devcathlon.engine.event.Event;
import org.hackystat.devcathlon.engine.event.EventException;
import org.hackystat.devcathlon.engine.event.EventScore;
import org.hackystat.devcathlon.engine.team.Team;
import org.hackystat.devcathlon.engine.user.User;
import org.hackystat.devcathlon.engine.user.UserManager;
import org.hackystat.sensorbase.resource.projects.jaxb.Project;
import org.hackystat.utilities.logger.HackystatLogger;
import org.hackystat.utilities.tstamp.Tstamp;
import org.hackystat.devcathlon.engine.match.Match;

/**
 * Penalizes teams with at least one member who is not putting in sufficient development time, thus
 * "missing in action".
 * <p>
 * Requires the following properties in the configuration.xml file for this event:
 * <ul>
 * <li>OneMember: 10 point loss for when a member who has not developed within 72 hours.
 * <li>AllMember: 20 point loss for when an entire team has not developed within 72 hours.
 * <li>TimePeriod: An integer indicating the number of days for non participation, such as 3.
 * <p>
 * </ul>
 * Specification:
 * <ul>
 * <li>Wake up once every day</li>
 * <li>Check for development for each member of the team.</li>
 * <li>Takeaway 10 points from the team with a member who has not develop within 72 hours.</li>
 * <li>Bonus:
 * <ul>
 * <li>If no member of the team develops within 72 hours, deduct 20 points.</li>
 * </ul>
 * </li>
 * </ul>
 * 
 * @author Daniel Arakaki
 * 
 */
public class NoMias extends Event {

  /** Support serialization. */
  private static final long serialVersionUID = 1L;

  /** A name for the start event. */
  public static final String eventName = "No MIAs";

  /** A description for the start event. */
  public static final String description = "Penalize teams with \"missing in action\" members "
      + "who are not participating.";

  /** The property key for the one member who is not working. */
  public static final String oneNoDevTimeKey = "OneNoDevTime";

  /** One member point lost. */
  private int oneNoDevTime;

  /** The property key for all team members who are not working. */
  public static final String allNoDevTimeKey = "AllNoDevTime";

  /** Whole team point lost. */
  private int allNoDevTime;

  /** The development time check a 72 hour period. */
  public static final String timePeriodKey = "TimePeriod";

  /** The time period check for development. */
  private int timePeriod;

  /** A Map for user information. */
  private Map<String, Integer> devTimeMap;

  /**
   * Create a new event for No MIAs. Ensures all required properties are available.
   * 
   * @throws EventException If configuration cannot be built.
   */
  public NoMias() throws EventException {
    super(eventName, description);
    try {
      this.oneNoDevTime = Integer.parseInt(configManager.getProperty(eventName, oneNoDevTimeKey));
      this.allNoDevTime = Integer.parseInt(configManager.getProperty(eventName, allNoDevTimeKey));
      this.timePeriod = Integer.parseInt(configManager.getProperty(eventName, timePeriodKey));
    }
    catch (Exception e) {
      throw new EventException("Problem with configuring properties", e);
    }

    this.devTimeMap = new HashMap<String, Integer>();
  }

  /**
   * Return number of points for the event.
   * 
   * @param score The event score.
   * @return Total weighted points for the event.
   */
  @Override
  public int computePoints(EventScore score) {
    return ((score.getBaseScore() * this.baseScoreWeight) + (score.getBonusScore(allNoDevTimeKey)));
  }

  /**
   * Return the computed score of the event.
   * 
   * @param match An ongoing match.
   * @param team A team in a match.
   * @param timestamp The given time.
   * @return aScore The computed event score.
   * @throws EventException When there is problem computing the score.
   */
  @Override
  public EventScore computeScore(Match match, Team team, XMLGregorianCalendar timestamp)
      throws EventException {

    EventScore score = new EventScore(timestamp, match, team);
    Project project = match.getProject(team);
    DailyProjectDataClient dpdClient = match.getDailyProjectDataClient(team);
    String projectName = project.getName();
    String owner = project.getOwner();

    this.devTimeMap.put(owner, 0);

    HackystatLogger.getLogger("org.hackystat.devcathlon", "devcathlon").info(
        String.format("Getting dev time data for match %s and team %s", match.getName(), team
            .getName()));

    // Set now as the current time.
    XMLGregorianCalendar currentTime = Tstamp.makeTimestamp();

    int devTimeSum = 0;
    int teamSize = team.getMembers().size();
    User member[] = new User[teamSize];

    this.putUser(team, member);

    // Get user's information: dev time and user name.
    for (int i = 0, j = timePeriod; i < timePeriod; i++, j--) {
      try {
        DevTimeDailyProjectData devTimeDpd = dpdClient.getDevTime(owner, projectName, Tstamp
            .incrementDays(currentTime, -j));

        for (MemberData dataRef : devTimeDpd.getMemberData()) {
          // you should recode this part with the new parameter
          String user1 = this.getUserName(dataRef);
          this.devTimeMap.put(user1, dataRef.getDevTime().intValue());
          devTimeSum = this.devTimeMap.get(user1);
          devTimeSum += devTimeSum;
        }
      }
      catch (Exception e1) {
        throw new EventException(String.format("Error getting dev time for match %s and team %s",
            match.getName(), team.getName()), e1);
      }
    }

    this.checkDevTime(teamSize, member, owner, score, devTimeSum);

    return score;
  }

  /**
   * Check the user's dev time.
   * 
   * @param teamSize Size of the team.
   * @param member A team member.
   * @param owner The team's owner.
   * @param score The event's score.
   * @param devTimeSum The sum of the dev time.
   */
  private void checkDevTime(int teamSize, User[] member, String owner, EventScore score,
      int devTimeSum) {
    for (int x = 0, y = 1; (x < teamSize - 1) && (y < teamSize); x++) {
      if ((this.devTimeMap.get(member[x].getEmail()) == 0)
          && (this.devTimeMap.get(member[y].getEmail()) == 0) 
          && (this.devTimeMap.get(owner) == 0)) {
        score.setBaseScore(computeBaseScore(score, 0));
        score.addBonusScore(allNoDevTimeKey, computeBonusScore(score, 0));
      }
      else if ((this.devTimeMap.get(member[x].getEmail()) == 0)
          || (this.devTimeMap.get(member[y].getEmail()) == 0) 
          || (this.devTimeMap.get(owner) == 0)) {
        score.setBaseScore(computeBaseScore(score, 0));
      }
      else {
        score.setBaseScore(computeBaseScore(score, devTimeSum));
      }
    }
  }

  /**
   * Computes the base score from a check on development time. If a member of a team has not develop
   * code within a 72 hour period, return dayThreeloss.
   * 
   * @param score A given score.
   * @param devTimeSum A BigInteger for a sum of dev time.
   * @return A -10 base score if a user has a 0 development time within the given timePeriod. A 0
   * base score if a member has development time with the given timePeriod.
   */
  private int computeBaseScore(EventScore score, int devTimeSum) {
    if (devTimeSum == 0) {
      score.setExplanation("Someone has not developed in the past 72 hours!");
      return this.oneNoDevTime;
    }
    else {
      score.setExplanation("All members have dev time.");
      return 0;
    }
  }

  /**
   * Compute the extra decrement when all members are not developing.
   * 
   * @param score The EventScore
   * @param allDevTime A BigInteger a sum of all user's dev time.
   * @return The bonus score.
   */
  private int computeBonusScore(final EventScore score, int allDevTime) {
    if (allDevTime == 0) {
      score.setExplanation("No one on the team has developed in the past 72 hours!");
      return this.allNoDevTime;
    }
    else {
      return 0;
    }
  }

  /**
   * Return a parsed user's name.
   * 
   * @param memberRef An instance of MemberData.
   * @return The user name.
   */
  private String getUserName(MemberData memberRef) {
    String user = memberRef.getMemberUri();
    int userNameIndex = user.lastIndexOf('/');
    String userBuffer = user.substring(userNameIndex + 1);
    return userBuffer;
  }

  /**
   * Insert user into hash map.
   * 
   * @param team A team instance.
   * @param member An user array.
   */
  private void putUser(Team team, User[] member) {

    UserManager manager = UserManager.getInstance();

    // set up members
    Object objArray[] = team.getMembers().toArray();
    User userArray[] = new User[member.length];

    for (int i = 0; i < member.length; i++) {
      userArray[i] = (User) objArray[i];
      member[i] = manager.getUser(userArray[i].getEmail());
      this.devTimeMap.put(member[i].getEmail(), 0);
    }
  }
}
